package com.shelpe.services.sinbad.controller;

import java.io.IOException;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.shelpe.services.sinbad.controller.CampaignController.CampaignDto;
import com.shelpe.services.sinbad.controller.KeywordsController.PricingKeyword;
import com.shelpe.services.sinbad.dto.AdgroupAndRptAdgroupDto;
import com.shelpe.services.sinbad.dto.AdgroupAndRptAdgroupDto.rptAdgroupVo;
import com.shelpe.services.sinbad.dto.KeywordDto;
import com.shelpe.services.sinbad.dto.common.ListMybatisPage;
import com.shelpe.services.sinbad.entity.jpa.Adgroup;
import com.shelpe.services.sinbad.entity.jpa.AdgroupLimit;
import com.shelpe.services.sinbad.entity.jpa.AdgroupLog;
import com.shelpe.services.sinbad.entity.jpa.AdgroupSearchCrowd;
import com.shelpe.services.sinbad.entity.jpa.AdgroupTargeting;
import com.shelpe.services.sinbad.entity.jpa.AdgroupTargeting.Status;
import com.shelpe.services.sinbad.entity.jpa.Campaign;
import com.shelpe.services.sinbad.entity.jpa.Category;
import com.shelpe.services.sinbad.entity.jpa.Creative;
import com.shelpe.services.sinbad.entity.jpa.CreativeLog;
import com.shelpe.services.sinbad.entity.jpa.CreativesError;
import com.shelpe.services.sinbad.entity.jpa.DriveType;
import com.shelpe.services.sinbad.entity.jpa.KeywordsLog;
import com.shelpe.services.sinbad.entity.jpa.OnlineStatus;
import com.shelpe.services.sinbad.entity.jpa.SearchCrowdError;
import com.shelpe.services.sinbad.entity.jpa.SearchCrowdLog;
import com.shelpe.services.sinbad.entity.jpa.TargetTag;
import com.shelpe.services.sinbad.entity.jpa.TemplateTags;
import com.shelpe.services.sinbad.entity.jpa.User;
import com.shelpe.services.sinbad.entity.jpa.vo.AdgroupTargetingDtoVo;
import com.shelpe.services.sinbad.entity.jpa.vo.AdgroupVo;
import com.shelpe.services.sinbad.entity.jpa.vo.ConvertKeywordNewVo;
import com.shelpe.services.sinbad.entity.jpa.vo.CreativeVo;
import com.shelpe.services.sinbad.entity.jpa.vo.DisplayKeywordNewVo;
import com.shelpe.services.sinbad.entity.jpa.vo.DisplayKeywordOldVo;
import com.shelpe.services.sinbad.entity.jpa.vo.DisplayKeywordVo;
import com.shelpe.services.sinbad.entity.jpa.vo.KeywordsVo;
import com.shelpe.services.sinbad.entity.jpa.vo.PlatformVo;
import com.shelpe.services.sinbad.entity.jpa.vo.SearchCrowdVo;
import com.shelpe.services.sinbad.entity.mongo.SyncAdgroupLimit;
import com.shelpe.services.sinbad.model.AdgroupWithRptAdgroupDto;
import com.shelpe.services.sinbad.model.Keyword;
import com.shelpe.services.sinbad.model.KeywordExample;
import com.shelpe.services.sinbad.model.RptKeyword;
import com.shelpe.services.sinbad.model.RptKeywordParam;
import com.shelpe.services.sinbad.readdao.KeywordMapper;
import com.shelpe.services.sinbad.readdao.ReadRptKeywordMapper;
import com.shelpe.services.sinbad.repository.jpa.AdgroupLimitRepository;
import com.shelpe.services.sinbad.repository.jpa.AdgroupLogRepository;
import com.shelpe.services.sinbad.repository.jpa.AdgroupRepository;
import com.shelpe.services.sinbad.repository.jpa.AdgroupSearchCrowdRepository;
import com.shelpe.services.sinbad.repository.jpa.AdgroupTargetingRepository;
import com.shelpe.services.sinbad.repository.jpa.CampaignRepository;
import com.shelpe.services.sinbad.repository.jpa.CategoryRepository;
import com.shelpe.services.sinbad.repository.jpa.CreativeLogRepository;
import com.shelpe.services.sinbad.repository.jpa.CreativeRepository;
import com.shelpe.services.sinbad.repository.jpa.CreativesErrorRepository;
import com.shelpe.services.sinbad.repository.jpa.KeywordsLogRepository;
import com.shelpe.services.sinbad.repository.jpa.SearchCrowdErrorRepository;
import com.shelpe.services.sinbad.repository.jpa.SearchCrowdLogRepository;
import com.shelpe.services.sinbad.repository.jpa.TargetTagRepository;
import com.shelpe.services.sinbad.repository.jpa.TemplateTagsRepository;
import com.shelpe.services.sinbad.repository.jpa.UserRepository;
import com.shelpe.services.sinbad.repository.mongo.SyncAdgroupLimitRepository;
import com.shelpe.services.sinbad.service.PlatformService;
import com.shelpe.services.sinbad.service.RptKeywordService;
import com.shelpe.services.sinbad.service.impl.OopKeywordService;
import com.shelpe.services.sinbad.service.simbaproxy.AdgroupClient;
import com.shelpe.services.sinbad.service.simbaproxy.AdgroupClient.AdgroupOnlineStatusDto;
import com.shelpe.services.sinbad.service.simbaproxy.AdgroupClient.CreateAdgroupResponse;
import com.shelpe.services.sinbad.service.simbaproxy.AdgroupClient.DeleteAdgroupResponse;
import com.shelpe.services.sinbad.service.simbaproxy.AdgroupClient.UpdateAdgroupResponse;
import com.shelpe.services.sinbad.service.simbaproxy.CreativeClient;
import com.shelpe.services.sinbad.service.simbaproxy.CreativeClient.CreateCreativeResponse;
import com.shelpe.services.sinbad.service.simbaproxy.CreativeClient.CreativeRecordDto;
import com.shelpe.services.sinbad.service.simbaproxy.CreativeClient.UpdateCreativeResponse;
import com.shelpe.services.sinbad.service.simbaproxy.SimbaProxyBaseDto;
import com.shelpe.services.sinbad.service.simbaproxy.SyncClient;
import com.shelpe.services.sinbad.service.simbaproxy.UserClient;
import com.shelpe.services.sinbad.service.simbaproxy.UserClient.CreateSearchcrowdResponse;
import com.shelpe.services.sinbad.service.simbaproxy.UserClient.DelSearchcrowdResponse;
import com.shelpe.services.sinbad.service.simbaproxy.UserClient.GetSearchCrowdResponse;
import com.shelpe.services.sinbad.service.simbaproxy.UserClient.SearchCrowdDto;
import com.shelpe.services.sinbad.service.simbaproxy.UserClient.UpdateSearchcrowdPriceResponse;
import com.shelpe.services.sinbad.service.simbaproxy.UserClient.UpdateSearchcrowdStateResponse;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping("/sinbadservice")
@Slf4j
@Api(value = "推广组相关的API", consumes = "application/json", produces = "application/json")
public class AdgroupController {
	@Autowired
	private AdgroupRepository adgroupRepository;
	@Autowired
	private UserRepository userRepository;
	@Autowired
	private CreativeRepository creativeRepository;
	@Autowired
	private AdgroupClient adgroupClient;
	@Autowired
	@JsonProperty
	private ObjectMapper objectMapper;
	@Autowired
	private AdgroupLogRepository adgroupLogRepository;
	@Autowired
	private CreativeLogRepository creativeLogRepository;
	@Autowired
	private KeywordsLogRepository keywordsLogRepository;
	@Autowired
	private RptKeywordService rptKeywordService;
	@Autowired
	private PlatformService platformService;
	@Autowired
	private OopKeywordService keywordService;
	@Autowired
	private KeywordMapper keywordMapper;
	@Autowired
	private CreativeClient creativeClient;
	@Autowired
	private UserClient userClient;
	@Autowired
	private AdgroupSearchCrowdRepository adgroupSearchCrowdRepository;
	@Autowired
	private AdgroupTargetingRepository adgroupTargetingRepository;
	@Autowired
	private TemplateTagsRepository templateTagsRepository;
	@Autowired
	private SearchCrowdErrorRepository searchCrowdErrorRepository;
	@Autowired
	private TargetTagRepository targetTagRepository;
	@Autowired
	private CreativesErrorRepository creativesErrorRepository;
	@Autowired
	private CampaignRepository campaignRepository;
	@Autowired
	SearchCrowdLogRepository searchCrowdLogRepository;
	@Autowired
	private ReadRptKeywordMapper readRptKeywordMapper;
	@Autowired
	private CategoryRepository categoryRepository;
	@Autowired
	private CampaignController campaignController;
	@Autowired
	private CreativeController creativeController;
	@Autowired
	private SyncClient syncClient;
	@Autowired
	private AdgroupLimitRepository adgroupLimitRepository;
	@Autowired
	private SyncAdgroupLimitRepository syncAdgroupLimitRepository;

	/**
	 * 创建推广组
	 * 
	 * @param dto
	 * @return
	 */
	@ApiOperation(value = "创建推广组")
	@ApiResponses({ @ApiResponse(code = 500, message = "user_not_foud"),
			@ApiResponse(code = 500, message = "invalid_default_price") })
	@SuppressWarnings("unused")
	@RequestMapping(method = RequestMethod.POST, path = "/adgroups")
	@ResponseBody
	public AdgroupDto createAdgroup(@RequestBody AdgroupDto dto) {
		log.info("start create adgroup:{}", dto);
		User user = this.userRepository.findOne(dto.getUserID());
		if (user == null) {
			throw new IllegalArgumentException("user_not_foud");
		}
		Assert.isTrue(dto.getDefaultPrice() > 0, "invalid_default_price");

		log.debug("createAdgroup: {}", dto);
		Adgroup adgroup = new Adgroup();
		if (!(dto.getFirstCreativeImg().contains("http"))) {
			dto.setFirstCreativeImg("https://img.alicdn.com/bao/uploaded" + dto.getFirstCreativeImg());
		}
		CreateAdgroupResponse res = this.adgroupClient.createAdgroup(user.getNick(), dto.getCampaignID(),
				dto.getProductID(), dto.getDefaultPrice(), dto.getFirstCreativeTitle(), dto.getFirstCreativeImg());
		log.debug("start  sync taobao :{},{}", res, res.getError());
		if (res == null) {
			dto.setError(new SimbaProxyBaseDto.Error());
			dto.getError().setCode(-1);
			dto.getError().setMessage("taobao_response_is_null");
			dto.getError().setSubMessage("淘宝返回为空,没有效数据");
			return dto;
		}
		if (res.getError() != null) {
			dto.setError(res.getError());
			return dto;
		}
		if (res.getRes() != null && res.getRes().getAdgroup() != null) {
			AdgroupClient.AdgroupDto tbDto = res.getRes().getAdgroup();
			log.debug("sync adgroup to taobao: {}", tbDto);
			BeanUtils.copyProperties(dto, adgroup);
			adgroup.setAutoID(tbDto.getAdgroupID());
			BeanUtils.copyProperties(tbDto, adgroup);
			adgroup.setProductName(dto.getFirstCreativeTitle());
			adgroup.setProductUrl(dto.getFirstCreativeImg());
			adgroup.setLimit(dto.getLimit());
			adgroup.setProductType("");
			adgroup.setDriveType(DriveType.from(dto.getDriveType()));
			adgroup.setOfflineType(OnlineStatus.from(tbDto.getOfflineType()));
			adgroup.setOnlineStatus(OnlineStatus.from(tbDto.getOnlineStatus()));
			adgroup.setStatus(Adgroup.Status.from(dto.getStatus()));
			if (dto.getCampaignID() > 0) {
				Campaign campaign = this.campaignRepository.findOne(dto.getCampaignID());
				PlatformVo convertVo = platformService.unserialize(campaign.getPlatform());
				if (convertVo != null) {
					adgroup.setMobileDiscount(convertVo.getMobileDiscount());
				}
			}
			adgroup.setProductAttributes("");
			adgroup.setMaintainedDate(DateUtils.truncate(new Date(), Calendar.DATE));
			adgroup.setUpdatedTime(new Date());
			adgroup.setAdjustCount(0);
			adgroup.setNextTime(new Date());
			this.adgroupRepository.save(adgroup);
			dto.setAdgroupID(adgroup.getAutoID());
			/*
			 * 描述：创建推广组日志 adgroups_log 数据库：adgroups_log author:yechen
			 */
			AdgroupVo avo = new AdgroupVo();
			BeanUtils.copyProperties(adgroup, avo);
			AdgroupLog apo = new AdgroupLog();
			BeanUtils.copyProperties(adgroup, apo);
			apo.setAdgroupID(adgroup.getAutoID());
			apo.setCampaignID(adgroup.getCampaignID());
			apo.setUpdatedTime(new Date());
			if (adgroup.getDriveType() != null && adgroup.getDriveType().getValue() != 0) {
				apo.setAction(AdgroupLog.Action.AUTO_ADD);
			} else {
				apo.setAction(AdgroupLog.Action.MANUAL_ADD);
			}
			try {
				apo.setNote(this.objectMapper.writer().writeValueAsString(avo));
			} catch (JsonProcessingException e) {
				log.error("JsonProcessingException: {}", e);
				apo.setNote("");
			}

			this.adgroupLogRepository.save(apo);
			log.debug("sync create adgroup AdgroupLog: {}", apo);

			/**
			 * 增加创意模块 作用：author：yechen 数据库：creatives
			 */
			if (StringUtils.isNotBlank(dto.getSecondCreativeImg())
					&& StringUtils.isNotBlank(dto.getSecondCreativeTitle())) {
				CreativeDto creativeDto = new CreativeDto();
				this.checkCreative(dto.getSecondCreativeTitle(), dto.getSecondCreativeImg(), creativeDto);
				CreativeDto cDtoRes = this.createCreative(adgroup.getAutoID(), creativeDto);
				log.info("finish add second creative:{}", cDtoRes);
			}

			if (StringUtils.isNotBlank(dto.getThirdCreativeImg())
					&& StringUtils.isNotBlank(dto.getThirdCreativeTitle())) {
				CreativeDto creativeDto = new CreativeDto();
				this.checkCreative(dto.getThirdCreativeTitle(), dto.getThirdCreativeImg(), creativeDto);
				CreativeDto cDtoRes = this.createCreative(adgroup.getAutoID(), creativeDto);
				log.info("finish add third creative:{}", cDtoRes);
			}

			if (StringUtils.isNotBlank(dto.getFourCreativeImg())
					&& StringUtils.isNotBlank(dto.getFourCreativeTitle())) {
				CreativeDto creativeDto = new CreativeDto();
				this.checkCreative(dto.getFourCreativeTitle(), dto.getFourCreativeImg(), creativeDto);

				CreativeDto cDtoRes = this.createCreative(adgroup.getAutoID(), creativeDto);
				log.info("finish add four creative:{}", cDtoRes);
			}
			// fetch crative
			this.creativeController.fetchCreative(adgroup.getAutoID());
		} else {
			if (res != null && res.getError() != null) {
				log.error("fail to create adgroup as sync: {}", "(原因为" + res.getError().getSubMessage() + ")");
				dto.setError(res.getError());
			}
		}

		log.debug("finish to create adgroup as sync : {}", dto);
		return dto;
	}

	public void checkCreative(String creativeTitle, String creativeImg, CreativeDto creativeDto) {
		if (StringUtils.isNotBlank(creativeTitle) && creativeImg.contains("http:") == true
				&& creativeImg.contains("https:") == true) {
			creativeImg = creativeImg.replaceAll("http:", "");
		}
		creativeDto.setCreativeName(creativeTitle);
		creativeDto.setCreativeImgUrl(creativeImg);
	}

	@ApiOperation(value = "更新推广组状态")
	@RequestMapping(method = RequestMethod.PUT, path = "/adgroups/{adgroup_id}/@status")
	@ResponseBody
	public AdgroupDto updateAdgroupStatus(@PathVariable("adgroup_id") long adgroupID, @RequestBody AdgroupDto dto) {
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.notNull(adgroup, "adgroup_not_foud");

		log.debug("updateAdgroupStatus: {}, {}", adgroupID, dto);
		adgroup.setStatus(Adgroup.Status.from(dto.getStatus()));
		adgroup.setUpdatedTime(new Date());
		this.adgroupRepository.save(adgroup);

		BeanUtils.copyProperties(adgroup, dto);
		return dto;
	}

	/**
	 * 修改推广组
	 * 
	 * @param adgroupID
	 * @param dto
	 * @return
	 */
	@ApiOperation(value = "更新推广组")
	@RequestMapping(method = RequestMethod.PUT, path = "/adgroups/{adgroup_id}")
	@ResponseBody
	public AdgroupDto updateAdgroup(@PathVariable("adgroup_id") long adgroupID, @RequestBody AdgroupDto dto) {
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.notNull(adgroup, "adgroup_not_foud");

		if (dto.getStatus() > -1) {
			adgroup.setStatus(Adgroup.Status.from(dto.getStatus()));
		}
		log.info("log adgroup_maintaied:{}>>{}", dto.getMaintainedDate(), adgroup.getMaintainedDate());
		if (dto.getMaintainedDate() != null && dto.getMaintainedDate() != adgroup.getMaintainedDate()) {
			adgroup.setMaintainedDate(dto.getMaintainedDate());

		}

		if (dto.getNextTime() != null && dto.getNextTime() != adgroup.getNextTime()) {
			adgroup.setNextTime(dto.getNextTime());
			this.adgroupRepository.save(adgroup);
		}

		log.info("log adgroup_limit:{}>>{}", dto.getLimit(), adgroup.getLimit());
		if (dto.getLimit() != 0 && dto.getLimit() != adgroup.getLimit()) {
			AdgroupVo avo = new AdgroupVo();
			avo.setOldLimit(adgroup.getLimit());
			avo.setNewLimit(dto.getLimit());
			avo.setProductName(adgroup.getProductName());

			adgroup.setLimit(dto.getLimit());
			this.adgroupRepository.save(adgroup);
			AdgroupLog apo = new AdgroupLog();
			BeanUtils.copyProperties(adgroup, apo);
			apo.setAdgroupID(adgroup.getAutoID());
			apo.setCampaignID(adgroup.getCampaignID());
			apo.setUpdatedTime(new Date());
			apo.setAction(AdgroupLog.Action.MANUAL_UPDATE_MAXPRICE);
			try {
				apo.setNote(this.objectMapper.writer().writeValueAsString(avo));
			} catch (JsonProcessingException e) {
				log.error("JsonProcessingException: {}", e);
				apo.setNote("");
			}
			this.adgroupLogRepository.save(apo);

			CampaignDto camDto = new CampaignDto();
			camDto.setCampaignLimit(dto.getLimit());
			List<String> actionTypes = new ArrayList<String>();
			actionTypes.add("auto");
			this.campaignController.updateCampaign(actionTypes, adgroup.getCampaignID(), camDto);

			/** 推广组最高限价和关键词价格表 */
			AdgroupLimit adgroupLimit = this.adgroupLimitRepository.findByAdgroupID(adgroupID);
			if (adgroupLimit != null) {
				adgroupLimit.setMaxPrice(dto.getLimit());
				adgroupLimit.setStatus(AdgroupLimit.Status.Running);
				this.adgroupLimitRepository.save(adgroupLimit);
			}

		}
		log.info("log adgroup_driverType:{}>>{}", dto.getDriveType(), adgroup.getDriveType().getValue());
		if (dto.getDriveType() >= 0 && dto.getDriveType() != adgroup.getDriveType().getValue()) {
			AdgroupVo avo = new AdgroupVo();
			avo.setOldDriveType(adgroup.getDriveType().getValue());
			avo.setNewDriveType(dto.getDriveType());
			avo.setProductName(adgroup.getProductName());

			adgroup.setPreviousAutoType(adgroup.getDriveType());
			adgroup.setDriveType(DriveType.from(dto.getDriveType()));
			adgroup.setUpdatedTime(new Date());
			adgroup.setChangeTime(new Date());
			this.adgroupRepository.save(adgroup);

			AdgroupLog po = new AdgroupLog();
			BeanUtils.copyProperties(adgroup, po);
			po.setAdgroupID(adgroup.getAutoID());
			po.setCampaignID(adgroup.getCampaignID());
			po.setUpdatedTime(new Date());
			po.setAction(AdgroupLog.Action.SWITCH_DRIVE_TYPE);
			try {
				po.setNote(this.objectMapper.writer().writeValueAsString(avo));
			} catch (JsonProcessingException e) {
				log.error("JsonProcessingException: {}", e);
				po.setNote("");
			}

			this.adgroupLogRepository.save(po);
			log.debug("sync update adgroup AdgroupLog: {}", po);
		}
		if (dto.getAdjustCount() > 0 && dto.getAdjustCount() != adgroup.getAdjustCount()) {
			adgroup.setAdjustCount(dto.getAdjustCount());
		}

		log.info("log adgroup_LineStatus:{}>>{}", dto.getLineStatus(), adgroup.getOnlineStatus().getValue());
		if (StringUtils.isNotBlank(dto.getLineStatus())
				&& !(dto.getLineStatus().equals(adgroup.getOnlineStatus().getValue()))) {
			AdgroupLog po = new AdgroupLog();
			if (adgroup.getOnlineStatus().getValue().equals("online")) {
				po.setAction(AdgroupLog.Action.MANUAL_PAUSE);
			} else {
				po.setAction(AdgroupLog.Action.MANUAL_OPEN);
			}

			UpdateAdgroupResponse res = this.adgroupClient.updateAdgroupOnlineStatus(adgroup.getNick(), adgroupID,
					dto.getLineStatus());
			if (res != null && res.getRes() != null && res.getRes().getAdgroup() != null
					&& res.getRes().getAdgroup().getOnlineStatus() != null) {
				AdgroupOnlineStatusDto tbOnlineStatusDto = res.getRes().getAdgroup();
				adgroup.setOnlineStatus(OnlineStatus.from(tbOnlineStatusDto.getOnlineStatus()));
				adgroup.setOfflineType(OnlineStatus.from(tbOnlineStatusDto.getOfflineType()));
				adgroup.setUpdatedTime(new Date());
				this.adgroupRepository.save(adgroup);

				AdgroupVo avo = new AdgroupVo();
				avo.setProductName(adgroup.getProductName());
				BeanUtils.copyProperties(adgroup, avo);
				BeanUtils.copyProperties(adgroup, po);
				po.setAdgroupID(adgroup.getAutoID());
				po.setCampaignID(adgroup.getCampaignID());
				po.setUpdatedTime(new Date());
				try {
					po.setNote(this.objectMapper.writer().writeValueAsString(avo));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException: {}", e);
					po.setNote("");
				}

				this.adgroupLogRepository.save(po);
				log.debug("sync update adgroup AdgroupLog: {}", po);
			}

			if (res != null && res.getError() != null) {
				log.error("updateAdgroupOnlineStatus:{}", res.getError().getSubMessage());
			}

		}

		adgroup.setUpdatedTime(new Date());
		this.adgroupRepository.save(adgroup);
		BeanUtils.copyProperties(adgroup, dto);
		dto.setLineStatus(adgroup.getOnlineStatus().getValue());
		dto.setDriveType(adgroup.getDriveType().getValue());

		return dto;

	}

	/**
	 * 查看某个推广组
	 * 
	 * @param adgroupID
	 * @return
	 */
	@ApiOperation(value = "查看某个推广组")
	@ApiResponses(@ApiResponse(code = 500, message = "invalid_adgroup_id"))
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}")
	@ResponseBody
	public AdgroupDto findAdgroup(@PathVariable("adgroup_id") long adgroupID) {
		Assert.isTrue(adgroupID > 0, "invalid_adgroup_id");
		AdgroupDto dto = new AdgroupDto();
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		String[] arr = adgroup.getCategoryIDs().split(" ");
		Long[] longs = new Long[arr.length];
		for (int i = 0; i < arr.length; i++) {
			longs[i] = Long.parseLong(arr[i]);
		}
		List<Long> catIDs = java.util.Arrays.asList(longs);
		List<Category> categorys = this.categoryRepository.findByCategoryIDInOrderByLevelAsc(catIDs);
		Assert.notNull(categorys, "invalid_category");
		if (categorys.size() > 1) {
			String catName = "";
			for (Category cat : categorys) {
				if (StringUtils.isBlank(catName)) {
					catName = cat.getName();
				} else {
					catName = catName + ">" + cat.getName();
				}
			}
			dto.setCategoryName(catName);
		} else {
			dto.setCategoryName(categorys.get(0).getName());
		}

		BeanUtils.copyProperties(adgroup, dto);
		dto.setAdgroupID(adgroup.getAutoID());
		dto.setDriveType(adgroup.getDriveType().getValue());

		return dto;
	}

	/**
	 * 删除某个推广组
	 * 
	 * @param adgroupID
	 * @return
	 */
	@ApiOperation(value = "删除一个推广组")
	@ApiResponses(@ApiResponse(code = 500, message = "adgroup_not_exist"))
	@RequestMapping(method = RequestMethod.DELETE, path = "/adgroups/{adgroup_id}")
	@ResponseBody
	public AdgroupDto delAdgroup(@PathVariable("adgroup_id") long adgroupID) {
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.notNull(adgroup, "adgroup_not_exist");

		AdgroupDto dto = new AdgroupDto();
		DeleteAdgroupResponse res = this.adgroupClient.deleteAdgroup(adgroup.getNick(), adgroupID);
		if (res != null && res.getRes() != null && res.getRes().getAdgroup() != null) {
			BeanUtils.copyProperties(adgroup, dto);
			dto.setAdgroupID(adgroup.getAutoID());
			this.adgroupRepository.delete(adgroup);

			/** 推广组最高限价和关键词价格表 */
			AdgroupLimit adgroupLimit = this.adgroupLimitRepository.findByAdgroupID(adgroupID);
			if (adgroupLimit != null) {
				this.adgroupLimitRepository.delete(adgroupLimit);
			}

			AdgroupLog adgroupLog = new AdgroupLog();
			adgroupLog.setAdgroupID(dto.getAdgroupID());
			adgroupLog.setAction(AdgroupLog.Action.MANUAL_DEL);
			adgroupLog.setCampaignID(dto.getCampaignID());
			adgroupLog.setUpdatedTime(new Date());

			AdgroupVo avo = new AdgroupVo(); // 存入log表的note字段的json值的类
			BeanUtils.copyProperties(dto, avo);
			avo.setProductName(adgroup.getProductName());
			avo.setAutoID(dto.getAdgroupID());
			try {
				adgroupLog.setNote(this.objectMapper.writer().writeValueAsString(avo));
			} catch (JsonProcessingException e) {
				log.error("JsonProcessingException: {}", e);
				adgroupLog.setNote("");
			}
			this.adgroupLogRepository.save(adgroupLog);
			log.debug("sync del adgroup to AdgroupLog: {}", adgroupLog);
		}
		if (res != null && res.getError() != null && res.getError().getSubMessage().equals("推广组对象不存在")) {
			this.adgroupRepository.delete(adgroup);
		}

		return dto;
	}

	@ApiOperation(value = "获取某个计划下的所有推广组")
	@RequestMapping(method = RequestMethod.GET, path = "/campaigns/{campaign_id}/adgroups")
	@ResponseBody
	public ListMybatisPage<AdgroupAndRptAdgroupDto> findAllAdgroup(@PathVariable("campaign_id") long campaignID,
			@RequestParam(name = "page_num", defaultValue = "0") int page,
			@RequestParam(name = "page_size", defaultValue = "50") int size,
			@RequestParam(name = "days", required = false, defaultValue = "0") int days,
			@RequestParam(name = "start_date", required = false) String startDate,
			@RequestParam(name = "end_date", required = false) String endDate,
			@RequestParam(name = "sort", defaultValue = "adgroupStatus") String sort,
			@RequestParam(name = "direct", defaultValue = "desc") String direct,
			@RequestParam(name = "search_title", defaultValue = "", required = false) String title) {
		ListMybatisPage<AdgroupAndRptAdgroupDto> dtos = new ListMybatisPage<AdgroupAndRptAdgroupDto>();
		dtos.setContent(new ArrayList<AdgroupAndRptAdgroupDto>());
		if (days > 0) {
			Date yesterday = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1);
			startDate = DateFormatUtils.format(DateUtils.addDays(yesterday, -days + 1), "yyyy-MM-dd");
			endDate = DateFormatUtils.format(yesterday, "yyyy-MM-dd");
		}
		int limitStart = page * size;
		int limitEnd = (page + 1) * size;

		String device = "mobile";
		List<AdgroupWithRptAdgroupDto> mobileDtos = this.readRptKeywordMapper.selectByAdgroupAndRptAdgroup(campaignID,
				startDate, endDate, 4, 5, sort, direct, limitStart, limitEnd, device, title);

		device = "pc";
		List<AdgroupWithRptAdgroupDto> pcDtos = this.readRptKeywordMapper.selectByAdgroupAndRptAdgroup(campaignID,
				startDate, endDate, 1, 2, sort, direct, limitStart, limitEnd, device, title);
		/** 1，2表示pc，4，5表示mobile */

		device = "all";
		List<AdgroupWithRptAdgroupDto> AllDtos = this.readRptKeywordMapper.selectByAdgroupAndRptAdgroup(campaignID,
				startDate, endDate, 1, 5, sort, direct, limitStart, limitEnd, device, title);
		Map<Long, AdgroupAndRptAdgroupDto> dtoMap = new HashMap<Long, AdgroupAndRptAdgroupDto>();

		for (AdgroupWithRptAdgroupDto adgroup : AllDtos) {
			AdgroupAndRptAdgroupDto dto = new AdgroupAndRptAdgroupDto();
			dto.fromAdgroup(adgroup);
			dto.setAll(new rptAdgroupVo());
			dto.setMobile(new rptAdgroupVo());
			dto.setPc(new rptAdgroupVo());
			BeanUtils.copyProperties(adgroup, dto.getAll());
			dtoMap.put(adgroup.getAdgroupId(), dto);
			dtos.getContent().add(dto);
		}
		for (AdgroupWithRptAdgroupDto adgroup : mobileDtos) {
			AdgroupAndRptAdgroupDto dto = dtoMap.get(adgroup.getAdgroupId());
			if (dto != null) {
				BeanUtils.copyProperties(adgroup, dto.getMobile());
			}
		}
		for (AdgroupWithRptAdgroupDto adgroup : pcDtos) {
			AdgroupAndRptAdgroupDto dto = dtoMap.get(adgroup.getAdgroupId());
			if (dto != null) {
				BeanUtils.copyProperties(adgroup, dto.getPc());
			}
		}

		/** 获取数据的汇总数 */
		int sum = this.readRptKeywordMapper.countByAdgroupAndRptAdgroup(campaignID, startDate, endDate, 1, 5, title);
		dtos.from(page, size, sum, dtos.getContent().size());

		return dtos;
	}

	/**
	 * 推广组层面的操作记录
	 * 
	 * @param adgroupID
	 * @param type
	 * @param page
	 * @param size
	 * @return
	 */
	@ApiOperation(value = "推广组层面的操作记录")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "type", defaultValue = "adgroup", allowableValues = "adgroup,creative,keyword", paramType = "query", dataType = "string") })
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/actions")
	@ResponseBody
	public Page<LogDto<LogDto.LogNoteable>> findAdgroupLog(@PathVariable("adgroup_id") long adgroupID,
			@RequestParam(name = "type", defaultValue = "adgroup") String type,
			@RequestParam(name = "page_num", defaultValue = "0") int page,
			@RequestParam(name = "page_size", defaultValue = "50") int size) {
		PageRequest pr = new PageRequest(page, size);

		Page<LogDto<LogDto.LogNoteable>> dtos = null;
		Date today = DateUtils.truncate(new Date(), Calendar.DATE);
		SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm");

		if ("adgroup".equals(type)) {
			Page<AdgroupLog> logs = this.adgroupLogRepository.findByAdgroupIDAndUpdatedTimeAfterOrderByUpdatedTimeDesc(
					adgroupID, DateUtils.addDays(today, -14), pr);
			List<LogDto<LogDto.LogNoteable>> list = new ArrayList<LogDto<LogDto.LogNoteable>>();
			for (AdgroupLog alog : logs) {
				LogDto dto = new LogDto<AdgroupVo>();
				BeanUtils.copyProperties(alog, dto);
				dto.setNewValue(alog.getNewIntval());
				dto.setAction(alog.getAction().toDisplay());
				dto.setUpdatedTime(sdf.format(alog.getUpdatedTime()));
				if (!(alog.getNote().equals(""))) {
					try {
						dto.setNote(this.objectMapper.readerFor(AdgroupVo.class).readValue(alog.getNote()));
					} catch (IOException e) {
						log.error("IOException:{}", e);
					}
				}
				list.add(dto);
			}
			dtos = new PageImpl<LogDto<LogDto.LogNoteable>>(list, pr, logs.getTotalElements());
		} else if ("creative".equals(type)) {
			Page<CreativeLog> logs = this.creativeLogRepository
					.findByAdgroupIDAndUpdatedTimeAfterOrderByUpdatedTimeDesc(adgroupID, DateUtils.addDays(today, -14),
							pr);
			List<LogDto<LogDto.LogNoteable>> list = new ArrayList<LogDto<LogDto.LogNoteable>>();
			for (CreativeLog clog : logs) {
				LogDto dto = new LogDto<CreativeVo>();
				BeanUtils.copyProperties(clog, dto);
				dto.setAction(clog.getAction().toDisplay());
				dto.setUpdatedTime(sdf.format(clog.getUpdatedTime()));
				if (!(clog.getNote().equals(""))) {
					try {
						dto.setNote(this.objectMapper.readerFor(CreativeVo.class).readValue(clog.getNote()));
					} catch (IOException e) {
						log.error("IOException:{}", e);
					}
				}
				list.add(dto);
			}
			dtos = new PageImpl<LogDto<LogDto.LogNoteable>>(list, pr, logs.getTotalElements());
		} else if ("keyword".equals(type)) {
			Page<KeywordsLog> logs = this.keywordsLogRepository
					.findByAdgroupIDAndUpdatedTimeAfterOrderByUpdatedTimeDesc(adgroupID, DateUtils.addDays(today, -14),
							pr);
			List<LogDto<LogDto.LogNoteable>> list = new ArrayList<LogDto<LogDto.LogNoteable>>();
			for (KeywordsLog klog : logs) {
				LogDto dto = new LogDto<KeywordsVo>();
				BeanUtils.copyProperties(klog, dto);
				dto.setAction(klog.getAction().toDisplay());
				dto.setKeyword(klog.getKeyword());
				dto.setUpdatedTime(sdf.format(klog.getUpdatedTime()));
				dto.setNewValue(klog.getNewIntVal());
				if (!(klog.getNote().equals(""))) {
					try {
						DisplayKeywordVo dv = new DisplayKeywordVo();
						ConvertKeywordNewVo cnv = new ConvertKeywordNewVo();
						DisplayKeywordNewVo dnv = new DisplayKeywordNewVo();
						DisplayKeywordOldVo dov = new DisplayKeywordOldVo();
						if (klog.getNote().contains("\"new\":\"")) {
							cnv = this.objectMapper.readerFor(ConvertKeywordNewVo.class).readValue(klog.getNote());
							dv.setOldNote(platformService.unserializeKeyword(cnv.getOldNoteStr()));
							dv.setNewNote(platformService.unserializeKeyword(cnv.getNewNoteStr()));
						} else if (klog.getNote().contains("\"old\":\"")) {
							dnv = this.objectMapper.readerFor(DisplayKeywordNewVo.class).readValue(klog.getNote());
							dv.setNewNote(dnv.getNewNote());
							dv.setOldNote(platformService.unserializeKeyword(dnv.getOldNoteStr()));
						} else {
							dov = this.objectMapper.readerFor(DisplayKeywordOldVo.class).readValue(klog.getNote());
							dv.setNewNote(dov.getNewNote());
							dv.setOldNote(dov.getOldNote());
						}
						dto.setNote(dv);
					} catch (IOException e) {
						log.error("IOException:{}", e);
					}
				}
				list.add(dto);
			}
			dtos = new PageImpl<LogDto<LogDto.LogNoteable>>(list, pr, logs.getTotalElements());
		} else {
			Page<SearchCrowdLog> logs = this.searchCrowdLogRepository
					.findByAdgroupIDAndUpdatedTimeAfterOrderByUpdatedTimeDesc(adgroupID, DateUtils.addDays(today, -14),
							pr);
			List<LogDto<LogDto.LogNoteable>> list = new ArrayList<LogDto<LogDto.LogNoteable>>();
			for (SearchCrowdLog sclog : logs) {
				LogDto dto = new LogDto<SearchCrowdVo>();
				BeanUtils.copyProperties(sclog, dto);
				dto.setAction(sclog.getAction().toDisplay());
				dto.setUpdatedTime(sdf.format(sclog.getUpdatedTime()));
				if (!(sclog.getNote().equals(""))) {
					try {
						dto.setNote(this.objectMapper.readerFor(SearchCrowdVo.class).readValue(sclog.getNote()));
					} catch (IOException e) {
						log.error("IOException:{}", e);
					}
				}
				list.add(dto);
			}
			dtos = new PageImpl<LogDto<LogDto.LogNoteable>>(list, pr, logs.getTotalElements());
		}
		return dtos;
	}

	/**
	 * 创意的增加
	 * 
	 * @param creativeID
	 * @param dto
	 * @return
	 */
	@ApiOperation(value = "添加创意")
	@RequestMapping(method = RequestMethod.POST, path = "/adgroups/{adgroup_id}/creatives")
	@ResponseBody
	public CreativeDto createCreative(@PathVariable("adgroup_id") long adgroupID, @RequestBody CreativeDto dto) {
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		if (adgroup == null) {
			throw new IllegalArgumentException("adgroup_not_foud");
		}

		if (dto.getCreativeName() != null && dto.getCreativeImgUrl() != null) {
			Creative po = new Creative();
			CreateCreativeResponse ccRes = this.creativeClient.createCreatives(adgroup.getNick(), adgroupID,
					dto.getCreativeName(), dto.getCreativeImgUrl());
			if (ccRes != null && ccRes.getRes() != null && ccRes.getRes().getCreative() != null) {
				CreativeClient.CreativeDto ctdto = ccRes.getRes().getCreative();
				BeanUtils.copyProperties(ctdto, po);
				po.setImage(ctdto.getUrl());
				po.setTitle(ctdto.getTitle());
				po.setCreateTime(ctdto.getCreateTime() != null ? ctdto.getCreateTime() : new Date());
				po.setModifiedTime(ctdto.getModifiedTime() != null ? ctdto.getModifiedTime() : new Date());
				po.setAutoID(ctdto.getCreativeId());
				po.setAdgroupID(ctdto.getAdgroupId());
				po.setUserID(adgroup.getUserID());
				po.setCampaignID(adgroup.getCampaignID());
				po.setUpdatedTime(new Date());

				dto.setCreativeID(po.getAutoID());
				dto.setUserID(adgroup.getUserID());
				dto.setAdgroupID(adgroupID);

				this.creativeRepository.save(po);
				log.debug("finish to add creatives : {} ", po);

				CreativeVo creatVo = new CreativeVo();
				creatVo.setCreativeNewTitle(dto.getCreativeName());
				creatVo.setNewImage(dto.getCreativeImgUrl());
				CreativeLog cpo = new CreativeLog();
				cpo.setAdgroupID(adgroupID);
				cpo.setCreativeID(po.getAutoID());
				cpo.setUpdatedTime(new Date());

				if (adgroup.getDriveType() != null && adgroup.getDriveType().getValue() != 0) {
					cpo.setAction(CreativeLog.Action.AUTO_ADD);
				} else {
					cpo.setAction(CreativeLog.Action.MANUAL_ADD);
				}

				try {
					cpo.setNote(this.objectMapper.writer().writeValueAsString(creatVo));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException: {}", e);
					cpo.setNote("");
				}

				this.creativeLogRepository.save(cpo);
				log.debug("sync create creative CreativeLog: {}", cpo);
			}
			if (ccRes != null && ccRes.getError() != null) {
				CreativesError error = new CreativesError();
				error.setAdgroupID(adgroupID);
				error.setCreateTime(new Date());
				error.setCreativeUrl(dto.getCreativeImgUrl());
				error.setStep("add_creative");
				try {
					error.setNote(objectMapper.writeValueAsString(ccRes.getError()));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException:{}", e);
					error.setNote("");
				}
				this.creativesErrorRepository.save(error);
				log.error("fail to add creative: {},{},{}", "(原因为" + ccRes.getError().getSubMessage() + ")", dto,
						ccRes.getError());
				dto.setError(ccRes.getError());
			}
		}

		return dto;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class CreativeDto {
		@JsonProperty("adgroup_id")
		private long adgroupID;
		@JsonProperty("user_id")
		private long userID;
		@JsonProperty("creative_id")
		private long creativeID;
		@JsonProperty("creative_title")
		private String creativeName;
		@JsonProperty("creative_img_url")
		private String creativeImgUrl;
		private SimbaProxyBaseDto.Error error;
	}

	/**
	 * 创意的修改
	 * 
	 * @param creativeID
	 * @param dto
	 * @return
	 */
	@ApiOperation(value = "修改创意")
	@RequestMapping(method = RequestMethod.PUT, path = "/creatives/{creative_id}")
	@ResponseBody
	public CreativeDto updateCreative(@PathVariable("creative_id") long creativeID, @RequestBody CreativeDto dto) {
		Creative creative = this.creativeRepository.findOne(creativeID);
		Assert.notNull(creative, "creative_not_found");

		log.debug("update creative: {},{}", creative, dto);
		CreativeVo creatVo = new CreativeVo();
		if (dto.getCreativeName() != creative.getTitle() || dto.getCreativeImgUrl() != creative.getImage()) {
			UpdateCreativeResponse res = this.creativeClient.updateCreatives(creative.getNick(),
					creative.getAdgroupID(), creative.getAutoID(), dto.getCreativeName(), dto.getCreativeImgUrl());
			if (res != null && res.getRes() != null && res.getRes().getCreativeRecord() != null) {
				CreativeRecordDto tbDto = res.getRes().getCreativeRecord();

				creatVo.setCreativeOldTitle(creative.getTitle());
				creatVo.setCreativeNewTitle(tbDto.getTitle());
				creatVo.setOldImage(creative.getImage());
				creatVo.setNewImage(tbDto.getImage());

				creative.setTitle(tbDto.getTitle());
				creative.setImage(tbDto.getImage());
				creative.setUpdatedTime(new Date());

				this.creativeRepository.save(creative);
				log.debug("finish update creative:{}", creative);
				BeanUtils.copyProperties(creative, creatVo);

				CreativeLog cpo = new CreativeLog();
				cpo.setAdgroupID(creative.getAdgroupID());
				cpo.setCreativeID(creative.getAutoID());
				cpo.setUpdatedTime(new Date());
				cpo.setAction(CreativeLog.Action.MANUAL_UPDATE);
				try {
					cpo.setNote(this.objectMapper.writer().writeValueAsString(creatVo));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException: {}", e);
					cpo.setNote("");
				}

				this.creativeLogRepository.save(cpo);
				log.debug("sync create creative CreativeLog: {}", cpo);
			} else if (res != null && res.getError() != null) {
				CreativesError error = new CreativesError();
				error.setAdgroupID(creative.getAdgroupID());
				error.setCreativeID(creative.getAutoID());
				error.setCreativeUrl(dto.getCreativeImgUrl());
				error.setCreateTime(new Date());
				error.setStep("update_creative");
				try {
					error.setNote(objectMapper.writeValueAsString(res.getError()));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException:{}", e);
					error.setNote("");
				}
				log.error("fail to add creative: {},{},{}", "(原因为" + res.getError().getSubMessage() + ")", dto,
						res.getError());
				this.creativesErrorRepository.save(error);
				dto.setError(res.getError());
			}
		}

		return dto;
	}

	@ApiOperation(value = "推广组的效果数据趋势")
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/reports/trend")
	public @ResponseBody List<EffectMetricDto> reportsTrend(@PathVariable("adgroup_id") Long adgroupId,
			@RequestParam(required = false, defaultValue = "0") int days, @RequestParam(required = false) String device,
			@RequestParam(name = "start_date", required = false) String startDate,
			@RequestParam(name = "end_date", required = false) String endDate) {
		log.info("reportsTrend RequestParam adgroupId:{} days:{} device:{} startDate:{} endDate:{}", adgroupId, days,
				device, startDate, endDate);
		RptKeywordParam param = RptKeywordParam.init();
		param.setUserId(adgroupRepository.findOne(adgroupId).getUserID());
		param.setAdgroupId(adgroupId);
		if (device != null) {
			param.setupDevice(device);
		}
		if (days > 0) {
			param.setupDays(days);
		}
		if (startDate != null) {
			param.setupDate(startDate, endDate);
		}
		List<RptKeyword> rpts = rptKeywordService.getReportsTrendByDate(param);

		List<EffectMetricDto> dtos = new ArrayList<EffectMetricDto>();
		if (rpts != null) {
			for (RptKeyword rpt : rpts) {
				EffectMetricDto dto = new EffectMetricDto();
				dto.filterNull(rpt);
				dto.from(rpt);
				dtos.add(dto);
			}
		}
		return dtos;
	}

	@ApiOperation(value = "推广组效果数据摘要")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "device", defaultValue = "all", allowableValues = "pc, mobile, all", paramType = "query", dataType = "string") })
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/summaries/metric")
	public @ResponseBody EffectMetricDto summariesMetric(@PathVariable("adgroup_id") long adgroupID,
			@RequestParam(required = false, defaultValue = "0") int days, @RequestParam(required = false) String device,
			@RequestParam(name = "start_date", required = false) String startDate,
			@RequestParam(name = "end_date", required = false) String endDate) {
		log.info("summariesMetric RequestParam adgroupID:{} days:{} device:{} startDate:{} endDate:{}", adgroupID, days,
				device, startDate, endDate);
		RptKeywordParam param = RptKeywordParam.init();
		param.setUserId(adgroupRepository.findOne(adgroupID).getUserID());
		param.setAdgroupId(adgroupID);
		if (device != null) {
			param.setupDevice(device);
		}
		if (days > 0) {
			param.setupDays(days);
		}
		if (startDate != null) {
			param.setupDate(startDate, endDate);
		}
		RptKeyword rpt = rptKeywordService.getSummariesMetric(param);
		EffectMetricDto dto = new EffectMetricDto();
		if (rpt != null) {
			dto.filterNull(rpt);
			dto.from(rpt);
		}
		return dto;
	}

	@ApiOperation(value = "推广组下所有关键词以及效果数据")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "device", defaultValue = "all", allowableValues = "pc, mobile, all,score", paramType = "query", dataType = "string") })
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/reports/keywordStates")
	public @ResponseBody List<EffectMetricDto> keywordStates(@PathVariable("adgroup_id") Long adgroupId,
			@RequestParam(required = false, defaultValue = "0") int days, @RequestParam(required = false) String device,
			@RequestParam(name = "start_date", required = false) String startDate,
			@RequestParam(name = "end_date", required = false) String endDate) {
		log.info("keywordStates RequestParam adgroupId:{} days:{} device:{} startDate:{} endDate:{}", adgroupId, days,
				device, startDate, endDate);
		RptKeywordParam param = RptKeywordParam.init();
		param.setUserId(adgroupRepository.findOne(adgroupId).getUserID());
		param.setAdgroupId(adgroupId);
		if (device != null) {
			param.setupDevice(device);
		}
		if (days > 0) {
			param.setupDays(days);
		}
		if (startDate != null) {
			param.setupDate(startDate, endDate);
		}

		List<RptKeyword> rpts = rptKeywordService.getReportsTrend(param);
		List<EffectMetricDto> dtos = new ArrayList<EffectMetricDto>();
		if (rpts != null) {
			for (RptKeyword rpt : rpts) {
				EffectMetricDto dto = new EffectMetricDto();
				dto.filterNull(rpt);
				dto.from(rpt);
				dtos.add(dto);
			}
		}
		return dtos;
	}

	@ApiOperation(value = "推广组下关键词当天是否有同步质量得分")
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/syscurrDayScore")
	public @ResponseBody boolean syscurrDayScore(@PathVariable("adgroup_id") Long adgroupId) {
		RptKeywordParam param = RptKeywordParam.init();
		param.setUserId(adgroupRepository.findOne(adgroupId).getUserID());
		param.setAdgroupId(adgroupId);
		param.setupDevice("score");
		Date today = DateUtils.truncate(new Date(), Calendar.DATE);
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
		String todayStart = format.format(today) + " 00:00:00";
		String todayEnd = format.format(today) + " 23:59:59";
		param.setupDate(todayStart, todayEnd);

		Integer count = rptKeywordService.countCurrdaySysScore(param);
		if (count != null && count > 0) {
			return true;
		} else {
			return false;
		}

	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class AdgroupDto {

		@JsonProperty("adgroup_id")
		private long adgroupID;

		@JsonProperty("user_id")
		private long userID;
		private String nick;
		@JsonProperty("campaign_id")
		private long campaignID;
		@JsonProperty("product_id")
		private long productID;
		// @JsonProperty("title")
		// private String productName;
		// @JsonProperty("img_url")
		// private String productUrl;
		@JsonProperty("drive_type")
		private int driveType = -1;
		@JsonProperty("default_price")
		private int defaultPrice;

		/**
		 * below fields are only for update
		 */
		@JsonProperty("line_status")
		private String lineStatus;
		private int status;

		@JsonProperty("maintainDate")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai")
		private Date maintainedDate;
		@JsonProperty("next_time")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
		private Date nextTime;

		// @JsonProperty("creative_title")
		// private String creativeName;
		// @JsonProperty("creative_img_url")
		// private String creativeImgUrl;
		@JsonProperty("adjust_count")
		private int adjustCount;

		private int limit;
		@JsonProperty("category_name")
		private String categoryName;
		private SimbaProxyBaseDto.Error error;

		/** below fields are creative four */

		@JsonProperty("title")
		private String firstCreativeTitle;
		@JsonProperty("img_url")
		private String firstCreativeImg;

		@JsonProperty("creative_title")
		private String secondCreativeTitle;
		@JsonProperty("creative_img_url")
		private String secondCreativeImg;

		@JsonProperty("third_creative_title")
		private String thirdCreativeTitle;
		@JsonProperty("third_creative_img")
		private String thirdCreativeImg;

		@JsonProperty("four_creative_title")
		private String fourCreativeTitle;
		@JsonProperty("four_creative_img")
		private String fourCreativeImg;

	}

	@ApiOperation(value = "更新推广组同时关键词价格随着同步")
	@ApiResponses({ @ApiResponse(code = 500, message = "adgroup_not_foud"),
			@ApiResponse(code = 500, message = "no_limit"), @ApiResponse(code = 500, message = "user_not_foud") })
	@RequestMapping(method = RequestMethod.PUT, path = "/adgroups/{adgroup_id}/actions")
	@ResponseBody
	public DeferredResult<AdgroupResponse> updateAdgroupWithKeyword(@PathVariable("adgroup_id") long adgroupID) {
		long start = System.currentTimeMillis();
		final DeferredResult<AdgroupResponse> result = new DeferredResult<AdgroupResponse>();
		final AdgroupResponse res = new AdgroupResponse();
		result.onTimeout(new Runnable() {
			@Override
			public void run() {
				res.setStatus(false);
				res.setMessage("timeout");
				result.setResult(res);
				log.error("timeout updateAdgroupWithKeyword {},the cost:{}", adgroupID,
						(System.currentTimeMillis() - start));
			}
		});

		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		if (adgroup == null) {
			throw new IllegalArgumentException("adgroup_not_foud");
		}
		if (adgroup.getLimit() == 0) {
			throw new IllegalArgumentException("no_limit");
		}
		User user = this.userRepository.findOne(adgroup.getUserID());
		if (user == null) {
			throw new IllegalArgumentException("user_not_foud");
		}

		KeywordExample ex = new KeywordExample();
		ex.setUserId(adgroup.getUserID());
		ex.createCriteria().andAdgroupIdEqualTo(adgroupID);
		List<Keyword> keywords = this.keywordMapper.selectByExample(ex);

		List<PricingKeyword> pricingKeywords = new ArrayList<PricingKeyword>();
		for (Keyword key : keywords) {
			PricingKeyword pk = new PricingKeyword();
			pk.setKeywordID(key.getKeywordId());
			pk.setMatchScope(key.getMatchscope());
			pk.setIsDefaultPrice(key.getIsDefaultPrice() ? 1 : 0);
			pk.setMobileIsDefaultPrice(key.getMobileIsDefaultPrice());
			pk.setMaxPrice(key.getMaxPrice());
			pk.setMaxMobilePrice(key.getMaxMobilePrice());
			boolean needUpdate = false;
			if (adgroup.getLimit() < key.getMaxMobilePrice()) {
				pk.setMaxMobilePrice(adgroup.getLimit());
				needUpdate = true;
			}
			if (adgroup.getLimit() < key.getMaxPrice()) {
				pk.setMaxPrice(adgroup.getLimit());
				needUpdate = true;
			}
			if (needUpdate) {
				pricingKeywords.add(pk);
			}
		}

		if (pricingKeywords.size() > 0) {
			this.keywordService.price(user, pricingKeywords, new OopKeywordService.Callback() {
				@Override
				public void call(List<Keyword> keywords) {
					log.debug("finish setprice:{}", res);
					res.setStatus(true);
					result.setResult(res);
				}
			}, true, false);
		} else {
			res.setStatus(false);
			res.setMessage("no_keyword_to_pricing");
			result.setResult(res);
		}

		/** adgroupLimit更新后修改状态 */
		if (res.isStatus() || (res != null && StringUtils.isNotBlank(res.getMessage())
				&& res.getMessage().equals("no_keyword_to_pricing"))) {
			AdgroupLimit adgroupLimit = this.adgroupLimitRepository.findByAdgroupIDAndStatus(adgroupID,
					AdgroupLimit.Status.Running);
			if (adgroupLimit != null) {
				adgroupLimit.setStatus(AdgroupLimit.Status.Completed);
				this.adgroupLimitRepository.save(adgroupLimit);
			}
		}
		return result;
	}

	@Data
	public class AdgroupDtoTemp {
		private long adgroupID;
	}

	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/@sync")
	@ResponseBody
	public void syncAdgroupLimit() {
		SyncAdgroupLimit syncAdgroupLimit = this.syncAdgroupLimitRepository.findOne("adgrouplimit");
		if (syncAdgroupLimit == null) {
			syncAdgroupLimit = new SyncAdgroupLimit();
			syncAdgroupLimit.setId("adgrouplimit");
			syncAdgroupLimit.setType("adgrouplimit");
			syncAdgroupLimit.setUpdateTime(new Date());
		}

		List<BigInteger> adgroupIDs = this.adgroupLimitRepository.findInserviceNick(syncAdgroupLimit.getUpdateTime());
		List<AdgroupLimit> adgroupLimits = new ArrayList<AdgroupLimit>();
		for (int i = 0; i < adgroupIDs.size(); i++) {
			Adgroup adgroup = this.adgroupRepository.findOne(adgroupIDs.get(i).longValue());
			if (adgroup != null) {
				List<String> filters = new ArrayList<String>();
				filters.add("max_price");
				ListResponse<KeywordDto> reponse = this.findKeywords(adgroup.getAutoID(), filters, adgroup.getLimit(),
						0, null, null, null);
				if (reponse != null && reponse.getTotal() > 0) {
					AdgroupLimit adgroupLimit = new AdgroupLimit();
					adgroupLimit.setAdgroupID(adgroup.getAutoID());
					adgroupLimit.setMaxPrice(adgroup.getLimit());
					adgroupLimit.setCreateTime(new Date());
					adgroupLimit.setStatus(AdgroupLimit.Status.Running);
					adgroupLimits.add(adgroupLimit);
				}
			}
		}
		this.adgroupLimitRepository.save(adgroupLimits);
		syncAdgroupLimit.setUpdateTime(new Date());
		this.syncAdgroupLimitRepository.save(syncAdgroupLimit);
	}

	/**
	 * 获取关键词相关信息
	 * 
	 * @param adgroupID
	 * @param type
	 * @param maxPrice
	 * @return
	 */
	@ApiOperation(value = "获取关键词相关信息")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "filters", defaultValue = "max_price", allowableValues = "max_price", paramType = "query", dataType = "List<string>") })
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/keywords")
	@ResponseBody
	public ListResponse<KeywordDto> findKeywords(@PathVariable("adgroup_id") long adgroupID,
			@RequestParam(name = "filters", defaultValue = "max_price") List<String> filters,
			@RequestParam(name = "max_price", required = false, defaultValue = "0") int maxPrice,
			@RequestParam(name = "days", required = false, defaultValue = "0") int days,
			@RequestParam(name = "device", required = false, defaultValue = "all") String device,
			@RequestParam(name = "start_date", required = false) String startDate,
			@RequestParam(name = "end_date", required = false) String endDate) {
		ListResponse<KeywordDto> dtos = new ListResponse<KeywordDto>();
		dtos.setDatas(new ArrayList<KeywordDto>());

		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		if (maxPrice <= 0) {
			/** 同步推广组下面的移动质量得分 */
			try {
				this.syncClient.synckeywordScore(adgroup.getUserID(), adgroupID);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		Assert.notNull(adgroup, "adgroup_not_exist");
		KeywordExample ex = new KeywordExample();
		ex.setUserId(adgroup.getUserID());
		ex.createCriteria().andAdgroupIdEqualTo(adgroupID);
		List<Keyword> keywords = this.keywordMapper.selectByExample(ex);
		Map<Long, KeywordDto> dtoMap = new HashMap<Long, KeywordDto>();
		for (Keyword kw : keywords) {
			KeywordDto dto = new KeywordDto();
			dto.from(kw, adgroup.getMobileDiscount());
			if (kw.getMatchscope() != null) {
				dto.setMatchscope(kw.getMatchscope());
			}
			if (kw.getIsDefaultPrice()) {
				dto.setIsDefaultPrice(1);
			}
			dto.setTitle(adgroup.getProductName());
			dto.setUrl(adgroup.getProductUrl());
			dto.setProductID(adgroup.getProductID());
			dtoMap.put(kw.getKeywordId(), dto);
			dtos.getDatas().add(dto);
		}

		if (maxPrice > 0) {
			for (Keyword key : keywords) {
				boolean canAdd = true;
				boolean needUpdate = false;
				if (filters != null) {
					if (filters.indexOf("max_price") > -1) {
						canAdd = canAdd && (maxPrice < key.getMaxMobilePrice() || maxPrice < key.getMaxPrice());
						needUpdate = true;
					}
				}
				if (!canAdd || !needUpdate) {
					dtoMap.remove(key.getKeywordId());
				}
			}
		} else {
			RptKeywordParam param = new RptKeywordParam();
			param.setUserId(adgroup.getUserID());
			param.setAdgroupId(adgroupID);
			param.setupDevice(device);
			if (days > 0) {
				param.setupDays(days);
			}
			if (startDate != null) {
				param.setupDate(startDate, endDate);
			}
			List<RptKeyword> rpts = rptKeywordService.getReportsTrend(param);
			if (rpts != null && rpts.size() > 0) {
				for (RptKeyword rpt : rpts) {
					KeywordDto dto = dtoMap.get(rpt.getKeywordId());
					if (dto != null) {
						dto.from(rpt);
					}
				}
			}
		}

		// TODO: convert map to list
		dtos.setTotal(dtoMap.size());
		dtos.setStatus(true);

		return dtos;
	}

	@Data
	public static class AdgroupResponse {
		private boolean status;
		private String message;
	}

	/**
	 * 新建搜索溢价人群
	 * 
	 * @param adgroupID
	 * @param dto
	 * @return
	 */
	@ApiOperation(value = "新建搜索溢价人群")
	@RequestMapping(method = RequestMethod.POST, path = "/adgroups/{adgroup_id}/searchCrowds")
	@ResponseBody
	public AdgroupTargetingDto createSearchCrowds(@PathVariable("adgroup_id") long adgroupID,
			@RequestBody AdgroupTargetingDto dto) {
		Assert.notNull(adgroupID, "adgroup_not_found");
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		if (adgroup == null) {
			dto.setError(new SimbaProxyBaseDto.Error());
			dto.getError().setCode(-1);
			dto.getError().setMessage("adgroup_find_is_null");
			dto.getError().setSubMessage("没有该推广组");
			return dto;
		}

		List<AdgroupTargetingDtoVo.TagList> tagListVos = new ArrayList<AdgroupTargetingDtoVo.TagList>();
		List<AdgroupTargetingDtoVo.AdgroupTargetingTags> tagVos = new ArrayList<AdgroupTargetingDtoVo.AdgroupTargetingTags>();

		AdgroupTargetingDtoVo.AdgroupTargetingTags tagVo = new AdgroupTargetingDtoVo.AdgroupTargetingTags();
		tagVo.setCrowdDtos(new AdgroupTargetingDtoVo.CrowdDto());
		tagVo.setDiscount(dto.getDiscount());
		tagVo.setIsDefaultPrice(dto.getIsDefaultPrice());
		tagVo.getCrowdDtos().setCrowdName(dto.getCrowdName());
		AdgroupTargetingDtoVo.CrowdDto crowdVo = new AdgroupTargetingDtoVo.CrowdDto();
		crowdVo.setExtParam(new AdgroupTargetingDtoVo.ExtParam());
		crowdVo.getExtParam().setFirstCat(dto.getFirstCat());
		for (AdgroupTargetingDto.Tag tag : dto.getTagList()) {
			TemplateTags dim = this.templateTagsRepository.findByDimIDAndTagName(tag.getDimID(), tag.getTagName());
			AdgroupTargetingDtoVo.TagList tagListVo = new AdgroupTargetingDtoVo.TagList();
			tagListVo.setDimID(Long.toString(dim.getDimID()));
			tagListVo.setOptionGroupID(Long.toString(dim.getOptionGroupID()));
			tagListVo.setTagID(Long.toString(dim.getTagID()));
			tagListVo.setTagName(dim.getTagName());
			tagListVos.add(tagListVo);

			if (tagVo.getCrowdDtos().getTemplateID() == null) {
				tagVo.getCrowdDtos().setTemplateID(Long.toString(dim.getTemplateID()));
				tagVo.getCrowdDtos().setTagList(tagListVos);
				tagVos.add(tagVo);
			}
		}

		List<AdgroupTargeting> adgroupTargetings = new ArrayList<AdgroupTargeting>();
		List<AdgroupSearchCrowd> adgroupSearchCrowds = new ArrayList<AdgroupSearchCrowd>();
		List<SearchCrowdLog> crowdLogs = new ArrayList<SearchCrowdLog>();

		long start = System.currentTimeMillis();
		CreateSearchcrowdResponse res = this.userClient.createSearchcrowd(adgroup.getNick(), this.serialize(tagVos),
				adgroup.getAutoID());
		long end = System.currentTimeMillis();
		log.info("start create SearchCrowds:{}>>{}>>{}", res, tagVos, this.serialize(tagVos));
		if (res != null && res.getRes() != null && res.getRes().getTargetingtagsList() != null
				&& res.getRes().getTargetingtagsList().getTargetingtags() != null) {
			List<UserClient.SearchCrowdDto> tbDtos = res.getRes().getTargetingtagsList().getTargetingtags();
			log.info("sync taobao response:adgroupID:{}--res:{}", adgroup.getAutoID(), tbDtos);
			for (UserClient.SearchCrowdDto tbDto : tbDtos) {
				AdgroupTargeting adgroupTargeting = new AdgroupTargeting();
				AdgroupSearchCrowd adgroupSearchCrowd = new AdgroupSearchCrowd();
				adgroupTargeting.from(tbDto, adgroup);
				adgroupSearchCrowd.from(tbDto, adgroup);
				if (tbDto.getCrowds() != null) {
					adgroupSearchCrowd.setTemplateID(tbDto.getCrowds().getTemplateID());
				}

				/* 新建操作人群时日志 */
				SearchCrowdVo vo = new SearchCrowdVo();
				vo.setAutoID(adgroupTargeting.getTargetID());
				vo.setTargetName(adgroupTargeting.getCrowdName());
				vo.setNewDiscount(adgroupTargeting.getDiscount());

				SearchCrowdLog crowdLog = new SearchCrowdLog();
				crowdLog.setAdgroupID(adgroup.getAutoID());
				crowdLog.setTargetID(adgroupTargeting.getTargetID());
				crowdLog.setUpdatedTime(new Date());
				try {
					crowdLog.setNote(this.objectMapper.writeValueAsString(vo));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException:{}", e);
					crowdLog.setNote("");
				}
				if (adgroup.getDriveType() != DriveType.manual) {
					crowdLog.setAction(SearchCrowdLog.Action.AUTO_ADD);
				} else {
					crowdLog.setAction(SearchCrowdLog.Action.MANUAL_ADD);
				}

				adgroupSearchCrowd.setCrowdID(adgroupTargeting.getTargetID() - 1);
				adgroupTargetings.add(adgroupTargeting);
				adgroupSearchCrowds.add(adgroupSearchCrowd);
				crowdLogs.add(crowdLog);
			}
			this.adgroupSearchCrowdRepository.save(adgroupSearchCrowds);
			this.adgroupTargetingRepository.save(adgroupTargetings);
			this.searchCrowdLogRepository.save(crowdLogs);

		} else {
			if (res != null && res.getError() != null) {
				SearchCrowdError error = new SearchCrowdError();
				error.setRuntime(end - start);
				error.setUserID(adgroup.getUserID());
				error.setAdgroupID(adgroup.getAutoID());
				error.setMessage("create_search_crowd");
				error.setCreateTime(new Date());
				if (res != null && res.getError() != null) {
					dto.setError(res.getError());
					try {
						error.setNote(this.objectMapper.writeValueAsString(dto.getError()));
					} catch (JsonProcessingException e) {
						log.error("JsonProcessingException:{}", e);
						error.setNote("");
					}
				}
				this.searchCrowdErrorRepository.save(error);
				dto.setError(res.getError());
			}
		}

		return dto;
	}

	/**
	 * 查看搜索溢价人群
	 * 
	 * @param adgroupID
	 * @return
	 */
	@ApiOperation(value = "查看搜索溢价人群")
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/searchCrowds")
	@ResponseBody
	public ListResponse<CrowdDtoSearch> findSearchcrowd(@PathVariable("adgroup_id") long adgroupID) {
		Assert.isTrue(adgroupID > 0, "invalid_adgroup_id");
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.notNull(adgroup, "invalid_adgroup");

		ListResponse<CrowdDtoSearch> res = new ListResponse<CrowdDtoSearch>();
		res.setDatas(new ArrayList<CrowdDtoSearch>());
		res.setAdgroupID(adgroup.getAutoID());

		List<AdgroupTargeting> adgroupTargetings = this.adgroupTargetingRepository.findByAdgroupID(adgroup.getAutoID());
		List<AdgroupSearchCrowd> adgroupSearchCrowds = this.adgroupSearchCrowdRepository
				.findByAdgroupID(adgroup.getAutoID());
		if (adgroupTargetings != null && adgroupSearchCrowds != null) {
			for (AdgroupTargeting adgroupTargeting : adgroupTargetings) {
				CrowdDtoSearch dto = new CrowdDtoSearch();
				dto.setDiscount(adgroupTargeting.getDiscount() + 100);
				dto.setIsDefaultPrice(adgroupTargeting.getIsDefaultPrice());
				dto.setOnlineStatus(adgroupTargeting.getOnlineStatus());
				if (adgroupTargeting.getOnlineStatus() == Status.offline) {
					res.setOfflineNums(res.getOfflineNums() + 1);
				} else {
					res.setOnlineNums(res.getOnlineNums() + 1);
				}
				dto.setTargetID("target-" + adgroupTargeting.getTargetID());
				dto.setPriceMode(adgroupTargeting.getPriceMode());
				for (AdgroupSearchCrowd adgroupSearchCrowd : adgroupSearchCrowds) {
					if (adgroupSearchCrowd.getTargetID() == adgroupTargeting.getTargetID()) {
						dto.setCrowds(new CrowdDtoSearch.Crowd());
						dto.getCrowds().setBussiessType(adgroupSearchCrowd.getBizType());
						dto.getCrowds().setCrowdID("crowd-" + adgroupSearchCrowd.getCrowdID());
						dto.getCrowds().setFitDiscount(adgroupSearchCrowd.getFitDiscount());
						dto.getCrowds().setName(adgroupSearchCrowd.getCrowdName());
						dto.getCrowds().setTemplateID(adgroupSearchCrowd.getTemplateID());
						dto.getCrowds().setType(adgroupSearchCrowd.getType());
						dto.getCrowds().setCustID(adgroupSearchCrowd.getCustID());
					}
				}
				res.getDatas().add(dto);
			}
		}
		res.setTotal(res.getDatas().size());
		return res;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class CrowdDtoSearch {
		@JsonProperty("price_mode")
		private int priceMode;
		@JsonProperty("discount")
		private int discount; // 返回的溢价比例,乘的关系,discount=121,代表溢价21%
		@JsonProperty("is_default_price")
		private int isDefaultPrice; // 人群是否溢价1:不溢价;0:溢价
		@JsonProperty("online_status")
		private Status onlineStatus; // 人群上下线状态0:暂停, 1:启用(offline:暂停,online:开启)
		@JsonProperty("id")
		private String targetID; // 定向id,出价删除 改价,修改状态时用
		@JsonProperty("crowd")
		private Crowd crowds;

		@Data
		public static class Crowd {
			@JsonProperty("biz_type")
			private long bussiessType; // 业务类型
			@JsonProperty("cust_id")
			private long custID; // 用户id
			@JsonProperty("name")
			private String name; // 人群名称
			@JsonProperty("crowd_id")
			private String crowdID; // 人群id,报表用
			@JsonProperty("template_id")
			private long templateID; // 人群包的模板id
			@JsonProperty("type")
			private int type; // 人群类型
			@JsonProperty("fit_discount")
			private int fitDiscount; // 人群建议溢价,取值范围[5,300]
		}

		private SimbaProxyBaseDto.Error error;
	}

	/**
	 * 修改单个搜索人群 type_id 的格式:taget-taget_id
	 * 
	 * @param adgroupID
	 * @param crowdID
	 * @param dto
	 * @return
	 */
	@ApiOperation(value = "修改单个搜索人群")
	@ApiResponses({ @ApiResponse(code = 500, message = "invalid_type_id"),
			@ApiResponse(code = 500, message = "adgroup_not_found"),
			@ApiResponse(code = 500, message = "adgroup_find_is_null") })
	@RequestMapping(method = RequestMethod.PUT, path = "/adgroups/{adgroup_id}/searchCrowds/{type_id}")
	@ResponseBody
	public UpdateSearchCrowdDto updateSearchCrowd(@PathVariable("adgroup_id") long adgroupID,
			@PathVariable("type_id") String typeID, @RequestBody UpdateSearchCrowdDto dto) {
		Assert.notNull(typeID, "invalid_type_id");
		Assert.notNull(adgroupID, "adgroup_not_found");
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		if (adgroup == null) {
			dto.setError(new SimbaProxyBaseDto.Error());
			dto.getError().setCode(-1);
			dto.getError().setMessage("adgroup_find_is_null");
			dto.getError().setSubMessage("没有该推广组");
			return dto;
		}
		AdgroupTargeting adgroupTargetingeting = null;
		String[] str = typeID.split("-");
		if (str[0].contains("crowd")) {
			AdgroupSearchCrowd.AdgroupSearchCrowdPK  pk = new AdgroupSearchCrowd.AdgroupSearchCrowdPK();
			pk.setAdgroupID(adgroupID);
			pk.setCrowdID(NumberUtils.toLong(str[1]));
			AdgroupSearchCrowd crowd = this.adgroupSearchCrowdRepository.findOne(pk);
			
			Assert.notNull(crowd, "searchCrowd_not_found");
			adgroupTargetingeting = this.adgroupTargetingRepository.findOne(crowd.getTargetID());
		} else {
			adgroupTargetingeting = this.adgroupTargetingRepository.findOne(NumberUtils.toLong(str[1]));
		}

		if (adgroupTargetingeting == null) {
			throw new IllegalArgumentException("adgroupTargeting_not_foud");
		}

		List<Long> targetIDs = new ArrayList<Long>();
		targetIDs.add(adgroupTargetingeting.getTargetID());

		if (dto.getOnlineStatus() != null && !(dto.getOnlineStatus().equals(adgroupTargetingeting.getOnlineStatus()))) {
			dto.setStatus(true);
			log.info("start update crowd_online_status:{}", dto);
			long start = System.currentTimeMillis();
			UpdateSearchcrowdStateResponse res = this.userClient.updateSearchcrowdState(adgroup.getNick(), targetIDs,
					adgroup.getAutoID(), dto.getOnlineStatus().getValue());
			long end = System.currentTimeMillis();
			log.debug("sync taobao crowd:{}", res);
			if (res != null && res.getRes() != null && res.getRes().getTargetingtagsList() != null
					&& res.getRes().getTargetingtagsList().getTargetingtags() != null) {
				List<SearchCrowdDto> tbDtos = res.getRes().getTargetingtagsList().getTargetingtags();
				for (SearchCrowdDto tbDto : tbDtos) {
					/* 修改人群状态日志 */
					SearchCrowdVo vo = new SearchCrowdVo();
					vo.setOldStatus(adgroupTargetingeting.getOnlineStatus().name());
					vo.setNewStatus(dto.getOnlineStatus().name());
					vo.setTargetName(adgroupTargetingeting.getCrowdName());

					adgroupTargetingeting.setOnlineStatus(AdgroupTargeting.Status.from(tbDto.getOnlineStatus()));
					adgroupTargetingeting.setUpdatedTime(new Date());
					this.adgroupTargetingRepository.save(adgroupTargetingeting);

					SearchCrowdLog crowdLog = new SearchCrowdLog();
					crowdLog.setAdgroupID(adgroup.getAutoID());
					crowdLog.setTargetID(adgroupTargetingeting.getTargetID());
					crowdLog.setUpdatedTime(new Date());
					try {
						crowdLog.setNote(this.objectMapper.writeValueAsString(vo));
					} catch (JsonProcessingException e) {
						log.error("JsonProcessingException:{}", e);
						crowdLog.setNote("");
					}
					crowdLog.setAction(SearchCrowdLog.Action.AUTO_UPDATE_STATUS);
					this.searchCrowdLogRepository.save(crowdLog);
				}
			} else {
				dto.setStatus(false);
				SearchCrowdError error = new SearchCrowdError();
				error.setRuntime(end - start);
				error.setUserID(adgroup.getUserID());
				error.setAdgroupID(adgroup.getAutoID());
				error.setMessage("update_search_crowd_status");
				error.setCreateTime(new Date());
				if (res != null && res.getError() != null) {
					dto.setError(res.getError());
					try {
						error.setNote(this.objectMapper.writeValueAsString(dto.getError()));
					} catch (JsonProcessingException e) {
						log.error("JsonProcessingException:{}", e);
						error.setNote("");
					}
				}
				this.searchCrowdErrorRepository.save(error);
			}

		}

		if (dto.getDiscount() != 0 && dto.getDiscount() != adgroupTargetingeting.getDiscount()) {
			log.info("start update crowd_price:{}", dto);
			long start = System.currentTimeMillis();
			UpdateSearchcrowdPriceResponse res = this.userClient.updateSearchcrowdPrice(adgroup.getNick(), targetIDs,
					adgroup.getAutoID(), dto.getDiscount());
			long end = System.currentTimeMillis();
			log.debug("sync taobao crowd:{}", res);
			if (res != null && res.getRes() != null && res.getRes().getTargetingtagsList() != null
					&& res.getRes().getTargetingtagsList().getTargetingtags() != null) {
				dto.setStatus(true);
				List<SearchCrowdDto> tbDtos = res.getRes().getTargetingtagsList().getTargetingtags();
				for (SearchCrowdDto tbDto : tbDtos) {
					/* 修改人群状态日志 */
					SearchCrowdVo vo = new SearchCrowdVo();
					vo.setOldDiscount(adgroupTargetingeting.getDiscount());
					vo.setNewDiscount(dto.getDiscount());
					vo.setTargetName(adgroupTargetingeting.getCrowdName());

					adgroupTargetingeting.setDiscount(tbDto.getDiscount());
					adgroupTargetingeting.setUpdatedTime(new Date());
					this.adgroupTargetingRepository.save(adgroupTargetingeting);

					SearchCrowdLog crowdLog = new SearchCrowdLog();
					crowdLog.setAdgroupID(adgroup.getAutoID());
					crowdLog.setTargetID(adgroupTargetingeting.getTargetID());
					crowdLog.setUpdatedTime(new Date());
					try {
						crowdLog.setNote(this.objectMapper.writeValueAsString(vo));
					} catch (JsonProcessingException e) {
						log.error("JsonProcessingException:{}", e);
						crowdLog.setNote("");
					}
					crowdLog.setAction(SearchCrowdLog.Action.AUTO_UPDATE_DISCOUNT);
					this.searchCrowdLogRepository.save(crowdLog);
				}
			} else {
				dto.setStatus(false);
				SearchCrowdError error = new SearchCrowdError();
				error.setRuntime(end - start);
				error.setUserID(adgroup.getUserID());
				error.setAdgroupID(adgroup.getAutoID());
				error.setMessage("update_search_crowd_discount");
				error.setCreateTime(new Date());
				if (res != null && res.getError() != null) {
					dto.setError(res.getError());
					try {
						error.setNote(this.objectMapper.writeValueAsString(dto.getError()));
					} catch (JsonProcessingException e) {
						log.error("JsonProcessingException:{}", e);
						error.setNote("");
					}
				}
				this.searchCrowdErrorRepository.save(error);
			}

		}
		log.debug("finish update searchCrowd:{}", dto);
		return dto;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class UpdateSearchCrowdDto {
		@JsonProperty("online_status")
		private Status onlineStatus;
		private int discount;
		private boolean status;

		private SimbaProxyBaseDto.Error error;
	}

	/**
	 * 删除搜索人群
	 * 
	 * @param adgroupID
	 * @param crowdID
	 * @return
	 */
	@ApiOperation(value = "删除某个搜索人群")
	@RequestMapping(method = RequestMethod.DELETE, path = "/adgroups/{adgroup_id}/searchCrowds/{target_id}")
	@ResponseBody
	public SearchCrowdResponse delSearchCrowd(@PathVariable("adgroup_id") long adgroupID,
			@PathVariable("target_id") long targetID) {
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.isTrue(adgroup != null, "该宝贝不存在");
		List<AdgroupTargeting> adgroupTargetings = this.adgroupTargetingRepository.findByAdgroupID(adgroupID);
		Assert.isTrue(adgroupTargetings != null, "该宝贝下没有人群设置");

		AdgroupTargeting adgroupTargetingeting = this.adgroupTargetingRepository.findOne(targetID);
		if (adgroupTargetingeting == null) {
			throw new IllegalArgumentException("adgroupTargeting_not_foud");
		}
		AdgroupSearchCrowd adgroupSearchCrowd = this.adgroupSearchCrowdRepository
				.findByTargetID(adgroupTargetingeting.getTargetID());
		if (adgroupSearchCrowd == null) {
			throw new IllegalArgumentException("adgroupSearchCrowd_not_foud");
		}

		List<Long> targetIDs = new ArrayList<Long>();
		targetIDs.add(adgroupTargetingeting.getTargetID());

		SearchCrowdResponse res = new SearchCrowdResponse();
		long start = System.currentTimeMillis();
		DelSearchcrowdResponse tbRes = this.userClient.delSearchcrowd(adgroup.getNick(), targetIDs);
		long end = System.currentTimeMillis();
		if (tbRes != null && tbRes.getRes() != null) {
			SearchCrowdVo vo = new SearchCrowdVo();
			vo.setTargetName(adgroupTargetingeting.getCrowdName());

			SearchCrowdLog crowdLog = new SearchCrowdLog();
			crowdLog.setAdgroupID(adgroup.getAutoID());
			crowdLog.setTargetID(adgroupTargetingeting.getTargetID());
			crowdLog.setUpdatedTime(new Date());
			try {
				crowdLog.setNote(this.objectMapper.writeValueAsString(vo));
			} catch (JsonProcessingException e) {
				log.error("JsonProcessingException:{}", e);
				crowdLog.setNote("");
			}
			crowdLog.setAction(SearchCrowdLog.Action.AUTO_DEL);
			this.searchCrowdLogRepository.save(crowdLog);

			this.adgroupTargetingRepository.delete(adgroupTargetingeting);
			this.adgroupSearchCrowdRepository.delete(adgroupSearchCrowd);
		} else {
			SearchCrowdError error = new SearchCrowdError();
			error.setRuntime(end - start);
			error.setUserID(adgroup.getUserID());
			error.setAdgroupID(adgroup.getAutoID());
			error.setMessage("delete_search_crowd");
			error.setCreateTime(new Date());
			if (tbRes != null && tbRes.getError() != null) {
				res.setError(tbRes.getError());
				try {
					error.setNote(this.objectMapper.writeValueAsString(res.getError()));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException:{}", e);
					error.setNote("");
				}
			}
			this.searchCrowdErrorRepository.save(error);
		}
		res.setStatus(true);
		return res;

	}

	@ApiOperation(value = "删除推广组下的搜索人群")
	@RequestMapping(method = RequestMethod.DELETE, path = "/adgroups/{adgroup_id}/searchCrowds")
	@ResponseBody
	public SearchCrowdResponse delAdgroupToSearchCrowd(@PathVariable("adgroup_id") long adgroupID) {
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.isTrue(adgroup != null, "该宝贝不存在");
		List<AdgroupTargeting> adgroupTargetings = this.adgroupTargetingRepository.findByAdgroupID(adgroupID);
		List<AdgroupSearchCrowd> adgroupCrowds = this.adgroupSearchCrowdRepository.findByAdgroupID(adgroupID);
		Assert.isTrue(adgroupTargetings != null, "该宝贝下没有人群设置");
		Assert.isTrue(adgroupCrowds != null, "该宝贝下没有人群设置");

		List<AdgroupTargeting> adgroupTargetingetings = new ArrayList<AdgroupTargeting>();
		List<AdgroupSearchCrowd> adgroupSearchCrowds = new ArrayList<AdgroupSearchCrowd>();

		List<SearchCrowdLog> crowdLogs = new ArrayList<SearchCrowdLog>();
		List<Long> targetIDs = new ArrayList<Long>();
		for (AdgroupTargeting adgroupTargetingeting : adgroupTargetings) {
			targetIDs.add(adgroupTargetingeting.getTargetID());
			adgroupTargetingetings.add(adgroupTargetingeting);

			SearchCrowdVo vo = new SearchCrowdVo();
			vo.setTargetName(adgroupTargetingeting.getCrowdName());

			SearchCrowdLog crowdLog = new SearchCrowdLog();
			crowdLog.setAdgroupID(adgroup.getAutoID());
			crowdLog.setTargetID(adgroupTargetingeting.getTargetID());
			crowdLog.setUpdatedTime(new Date());
			try {
				crowdLog.setNote(this.objectMapper.writeValueAsString(vo));
			} catch (JsonProcessingException e) {
				log.error("JsonProcessingException:{}", e);
				crowdLog.setNote("");
			}
			crowdLog.setAction(SearchCrowdLog.Action.AUTO_DEL);
			crowdLogs.add(crowdLog);
		}
		for (AdgroupSearchCrowd adgroupSearchCrowd : adgroupCrowds) {
			adgroupSearchCrowds.add(adgroupSearchCrowd);
		}

		SearchCrowdResponse res = new SearchCrowdResponse();
		long start = System.currentTimeMillis();
		DelSearchcrowdResponse tbRes = this.userClient.delSearchcrowd(adgroup.getNick(), targetIDs);
		long end = System.currentTimeMillis();
		if (tbRes != null && tbRes.getRes() != null) {
			this.adgroupTargetingRepository.delete(adgroupTargetingetings);
			this.adgroupSearchCrowdRepository.delete(adgroupSearchCrowds);
			this.searchCrowdLogRepository.save(crowdLogs);
		} else {
			SearchCrowdError error = new SearchCrowdError();
			error.setRuntime(end - start);
			error.setUserID(adgroup.getUserID());
			error.setAdgroupID(adgroup.getAutoID());
			error.setMessage("delete_search_crowd");
			error.setCreateTime(new Date());
			if (tbRes != null && tbRes.getError() != null) {
				res.setError(tbRes.getError());
				try {
					error.setNote(this.objectMapper.writeValueAsString(res.getError()));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException:{}", e);
					error.setNote("");
				}
			}
			this.searchCrowdErrorRepository.save(error);
		}
		res.setStatus(true);
		return res;

	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class SearchCrowdResponse {
		private boolean status;
		private SimbaProxyBaseDto.Error error;
	}

	/**
	 * 同步搜索溢价人群
	 * 
	 * @param adgroupID
	 * @return
	 */
	@ApiOperation(value = "同步搜索溢价人群")
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/searchCrowds/@sync")
	@ResponseBody
	public ListResponse<SearchCrowdDto> syncSearchCrowd(@PathVariable("adgroup_id") long adgroupID) {
		Assert.isTrue(adgroupID > 0, "invalid_adgroup_id");
		Adgroup adgroup = this.adgroupRepository.findOne(adgroupID);
		Assert.notNull(adgroup, "invalid_adgroup");

		// 同步第一步:将数据库内的数据查找是否存在
		List<AdgroupTargeting> existingTargets = this.adgroupTargetingRepository.findByAdgroupID(adgroupID);
		List<AdgroupSearchCrowd> existingCrowds = this.adgroupSearchCrowdRepository.findByAdgroupID(adgroupID);
		// 第二步:调淘宝api，将返回的数据全部填充到数据库
		List<AdgroupTargeting> adgroupTargetings = new ArrayList<AdgroupTargeting>();
		List<AdgroupSearchCrowd> adgroupSearchCrowds = new ArrayList<AdgroupSearchCrowd>();
		ListResponse<SearchCrowdDto> dto = new ListResponse<SearchCrowdDto>();
		dto.setDatas(new ArrayList<SearchCrowdDto>());

		long start = System.currentTimeMillis();
		GetSearchCrowdResponse res = this.userClient.GetSearchcrowd(adgroup.getNick(), adgroup.getAutoID());
		long end = System.currentTimeMillis();
		if (res != null && res.getRes() != null && res.getRes().getTargetingtagsList() != null
				&& res.getRes().getTargetingtagsList().getTargetingtags() != null) {
			log.info("start sync crowds:{}", res.getRes().getTargetingtagsList().getTargetingtags());
			List<UserClient.SearchCrowdDto> tbDtos = res.getRes().getTargetingtagsList().getTargetingtags();
			for (UserClient.SearchCrowdDto tbDto : tbDtos) {
				AdgroupSearchCrowd existingCrowd = null;
				AdgroupTargeting existingTarget = null;
				if (tbDto.getCrowds() != null) {
					existingCrowd = this.filterByCrowdID(existingCrowds, tbDto.getCrowds().getName());
					existingTarget = this.filterByTargetID(existingTargets, tbDto.getCrowds().getName());
				}
				if (existingTarget != null) {
					existingTarget.from(tbDto, adgroup);
					adgroupTargetings.add(existingTarget);
				} else {
					AdgroupTargeting adgroupTargeting = new AdgroupTargeting();
					adgroupTargeting.from(tbDto, adgroup);
					adgroupTargetings.add(adgroupTargeting);
				}
				if (existingCrowd != null) {
					existingCrowd.from(tbDto, adgroup);
					adgroupSearchCrowds.add(existingCrowd);
				} else {
					AdgroupSearchCrowd adgroupSearchCrowd = new AdgroupSearchCrowd();
					adgroupSearchCrowd.from(tbDto, adgroup);
					adgroupSearchCrowd.setTemplateID(tbDto.getCrowds().getTemplateID());
					adgroupSearchCrowds.add(adgroupSearchCrowd);
				}
				dto.getDatas().add(tbDto);
			}
			existingCrowds.removeAll(adgroupSearchCrowds);
			existingTargets.removeAll(adgroupTargetings);
			this.adgroupSearchCrowdRepository.delete(existingCrowds);
			this.adgroupTargetingRepository.delete(existingTargets);
			this.adgroupSearchCrowdRepository.save(adgroupSearchCrowds);
			this.adgroupTargetingRepository.save(adgroupTargetings);
		} else {
			SearchCrowdError error = new SearchCrowdError();
			error.setRuntime(end - start);
			error.setUserID(adgroup.getUserID());
			error.setAdgroupID(adgroup.getAutoID());
			error.setMessage("sync_search_crowd");
			error.setCreateTime(new Date());
			if (res != null && res.getError() != null) {
				dto.setError(res.getError());
				try {
					error.setNote(this.objectMapper.writeValueAsString(dto.getError()));
				} catch (JsonProcessingException e) {
					log.error("JsonProcessingException:{}", e);
					error.setNote("");
				}
			}
			this.searchCrowdErrorRepository.save(error);
		}
		dto.setTotal(dto.getDatas().size());
		log.info("finish sync crowd_data:{},the cost:{} ms", dto.getTotal(), (end - start));
		return dto;
	}

	private AdgroupTargeting filterByTargetID(List<AdgroupTargeting> targets, String crowdName) {
		for (AdgroupTargeting target : targets) {
			if (target.getCrowdName() == crowdName) {
				return target;
			}
		}
		return null;
	}

	private AdgroupSearchCrowd filterByCrowdID(List<AdgroupSearchCrowd> crowds, String crowdName) {
		for (AdgroupSearchCrowd crowd : crowds) {
			if (crowd.getCrowdName() == crowdName) {
				return crowd;
			}
		}
		return null;
	}

	/**
	 * 查看推广组的搜索人群效果数据
	 * 
	 * @param adgroupID
	 * @param startDate
	 * @return
	 */
	@ApiOperation(value = "查看推广组的搜索人群效果数据")
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/searchCrowds/@metric")
	@ResponseBody
	public TargetTagDto findSearchCrowdWithAdgroupMetric(@PathVariable("adgroup_id") long adgroupID,
			@RequestParam("start_date") String startDate,
			@RequestParam(name = "end_date", required = false) String endDate,
			@RequestParam(name = "device", defaultValue = "all", required = false) String device) {
		List<TargetTagDto> dtos = new ArrayList<TargetTagDto>();

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date fromDate = null;
		Date toDate = null;
		try {
			fromDate = sdf.parse(startDate);
			if (StringUtils.isEmpty(endDate)) {
				toDate = sdf.parse(startDate);
			} else {
				toDate = sdf.parse(endDate);
			}
		} catch (ParseException e) {
			log.error("ParseException:{}", e);
		}
		// PC站内:1， PC站外:2 , 移动站内:4，移动站外:5 ps: pc:12 mobile:45
		List<TargetTag> targetTags = null;
		List<Integer> sourceAll = new ArrayList<Integer>();
		sourceAll.add(12);
		sourceAll.add(45);
		if (device.equals("pc")) {
			targetTags = this.targetTagRepository.findByAdgroupIDAndDateBetweenAndSource(adgroupID, fromDate, toDate,
					12);
		} else if (device.equals("mobile")) {
			targetTags = this.targetTagRepository.findByAdgroupIDAndDateBetweenAndSource(adgroupID, fromDate, toDate,
					45);
		} else {
			targetTags = this.targetTagRepository.findByAdgroupIDAndDateBetweenAndSourceIn(adgroupID, fromDate, toDate,
					sourceAll);
		}

		TargetTagDto dto = new TargetTagDto();
		dto.from(targetTags);

		log.debug("finish searchCrowdMetric:{}", dtos);
		return dto;
	}

	/**
	 * 查看搜索人群效果数据
	 * 
	 * @param adgroupID
	 * @param startDate
	 * @return
	 */
	@ApiOperation(value = "查看搜索人群效果数据")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "device", defaultValue = "all", allowableValues = "all,pc, mobile", paramType = "query", dataType = "string") })
	@RequestMapping(method = RequestMethod.GET, path = "/adgroups/{adgroup_id}/crowds/@metric")
	@ResponseBody
	public List<EffectMetricDto> findSearchCrowdMetric(@PathVariable("adgroup_id") long adgroupID,
			@RequestParam(name = "start_date", required = false) String startDate,
			@RequestParam(name = "end_date", required = false) String endDate,
			@RequestParam(name = "days", required = false, defaultValue = "0") int days,
			@RequestParam(name = "device", defaultValue = "all", required = false) String device) {
		List<EffectMetricDto> dtos = new ArrayList<EffectMetricDto>();

		// PC站内:1， PC站外:2 , 移动站内:4，移动站外:5 ps: pc:12 mobile:45
		List<TargetTag> targetTags = null;
		List<Integer> sourceAll = new ArrayList<Integer>();
		sourceAll.add(12);
		sourceAll.add(45);
		Date today = DateUtils.truncate(new Date(), Calendar.DATE);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

		if (StringUtils.isBlank(startDate) && days > 0) {
			String dateStr = sdf.format(DateUtils.addDays(today, -(days + 1)));
			Date date = null;
			try {
				date = sdf.parse(dateStr);
			} catch (ParseException e) {
				log.error("ParseException:{}", e);
			}
			if (device.equals("pc")) {
				targetTags = this.targetTagRepository.findByAdgroupIDAndSourceAndDateAfterOrderByDateDesc(adgroupID, 12,
						date);
			} else if (device.equals("mobile")) {
				targetTags = this.targetTagRepository.findByAdgroupIDAndSourceAndDateAfterOrderByDateDesc(adgroupID, 45,
						date);
			} else {
				targetTags = this.targetTagRepository.findByAdgroupIDAndSourceInAndDateAfterOrderByDateDesc(adgroupID,
						sourceAll, date);
				log.info("cal time:{}", date);
			}
		} else {
			Date fromDate = null;
			Date toDate = null;
			try {
				fromDate = sdf.parse(startDate);
				if (StringUtils.isEmpty(endDate)) {
					toDate = sdf.parse(startDate);
				} else {
					toDate = sdf.parse(endDate);
				}
			} catch (ParseException e) {
				log.error("ParseException:{}", e);
			}
			if (device.equals("pc")) {
				targetTags = this.targetTagRepository.findByAdgroupIDAndDateBetweenAndSource(adgroupID, fromDate,
						toDate, 12);
			} else if (device.equals("mobile")) {
				targetTags = this.targetTagRepository.findByAdgroupIDAndDateBetweenAndSource(adgroupID, fromDate,
						toDate, 45);
			} else {
				targetTags = this.targetTagRepository.findByAdgroupIDAndDateBetweenAndSourceIn(adgroupID, fromDate,
						toDate, sourceAll);
			}
		}

		if (targetTags != null) {
			Map<Long, List<TargetTag>> crowdTags = new HashMap<Long, List<TargetTag>>();
			for (TargetTag targetTag : targetTags) {
				List<TargetTag> tags = crowdTags.get(targetTag.getCrowdID());
				if (tags == null) {
					tags = new ArrayList<TargetTag>();
					crowdTags.put(targetTag.getCrowdID(), tags);
				}
				tags.add(targetTag);
			}
			for (Map.Entry<Long, List<TargetTag>> entry : crowdTags.entrySet()) {
				EffectMetricDto dto = new EffectMetricDto();
				dto.setCrowdID("crowd-" + entry.getKey());
				dto.from(entry.getValue());
				dtos.add(dto);
			}
		}

		log.debug("finish CrowdMetric:{}", dtos);
		return dtos;
	}

	@Data
	public static class TargetTagDto {
		private float cpc;// 平均点击花费
		private float ctr;// 点击率
		private long impressions;// 展现量
		private float cpm;// 千次点击
		private long click;// 点击量
		private long cost;// 总花费
		private float roi;// 投入产出比
		private long directPay;// 直接成交金额
		private long indirectPay;// 间接成交金额
		private long directPayCount;// 直接成交
		private long indirectPayCount;// 间接成交
		private long payTotalCount;// 总成交笔数
		private long payTotal; // 总成交金额
		private long favShopCount;// 店铺收藏
		private long favItemCount;// 宝贝收藏
		private long favTotal;// 总收藏数
		private long directCartTotal;// 直接购物车数
		private long indirectCartTotal;// 间接购物车数
		private long cartTotal;// 购物车总数
		private float coverage;// 点击转化率

		public void from(List<TargetTag> tags) {
			if (tags != null) {
				for (TargetTag tag : tags) {
					impressions = impressions + tag.getImpressions();
					cpm = cpm + tag.getCpm();
					click = click + tag.getClick();
					cost = cost + tag.getCost();
					directPay = directPay + tag.getDirectPay();
					indirectPay = indirectPay + tag.getIndirectPay();
					directPayCount = directPayCount + tag.getDirectPayCount();
					indirectPayCount = indirectPayCount + tag.getIndirectPayCount();
					favShopCount = favShopCount + tag.getFavShopCount();
					favItemCount = favItemCount + tag.getFavItemCount();
					directCartTotal = directCartTotal + tag.getDirectCartTotal();
					indirectCartTotal = indirectCartTotal + tag.getIndirectCartTotal();
				}
				payTotal = directPay + indirectPay;
				payTotalCount = directPayCount + indirectPayCount;
				cartTotal = directCartTotal + indirectCartTotal;
				favTotal = favShopCount + favItemCount;

				if (cost == 0) {
					roi = 0.0f;
				} else {
					roi = (float) payTotal / (float) cost;
				}
				if (click == 0) {
					cpc = 0.0f;
					ctr = 0.0f;
					coverage = 0.0f;
				} else {
					cpc = (float) cost / (float) click;
					ctr = (float) click / (float) impressions;
					coverage = (float) payTotalCount / (float) click;
				}
			}
		}
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class AdgroupTargetingDto {
		@JsonProperty("is_default_price")
		private int isDefaultPrice;
		@JsonProperty("discount")
		private int discount; // 溢价范围为[5,300],eg:若折扣为105,那么溢价discount为5
		@JsonProperty("name")
		private String crowdName;
		@JsonProperty("first_cat")
		private String firstCat;
		@JsonProperty("tags")
		private List<Tag> tagList;

		private SimbaProxyBaseDto.Error error;

		public void addCrowd(Tag tag) {
			if (this.tagList == null) {
				this.tagList = new ArrayList<Tag>();
			}
			this.tagList.add(tag);
		}

		@Data
		public static class Tag {
			@JsonProperty("dim_id")
			private long dimID;
			@JsonProperty("tag_name")
			private String tagName;
		}
	}

	public String serialize(List<AdgroupTargetingDtoVo.AdgroupTargetingTags> targetings) { // 序列化，对象到字符串的过程
		try {
			return objectMapper.writeValueAsString(targetings);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		return "";
	}
}